﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using RoMFramework.RomAPI;
using System.Reflection;

namespace RoMFramework.Hacks
{
    public class HackManager
    {
        private RomClient m_Client;
        private byte[] m_NoFallOriginalBytes, m_SwimHackOriginalBytes;
        private float OriginalSpeed, OriginalWallClimb;
        public float WallClimbAngle;
        public bool SwimHackEnabled, NoFallEnabled, StealthHackEnabled, WallClimbEnabled;
        public Dictionary<Keys, HackBase> Hacks = new Dictionary<Keys, HackBase>();

        /// <summary>
        /// Initialises the Hack Manager
        /// </summary>
        /// <param name="client">Client object reference</param>
        public HackManager(RomClient client)
        {
            m_Client = client;
            OriginalSpeed = m_Client.Player.Speed;
            OriginalWallClimb = m_Client.Player.ClimbAngle;
            WallClimbAngle = 0.1f;
        }

        #region FailedAttemptAtExtensibility

        //public void LoadHacks(Assembly[] asms = null)
        //{
        //    //Load the user specified hacks first
        //    try
        //    {
        //        if (asms != null)
        //        {
        //            foreach (var asm in asms)
        //            {
        //                LoadHacksFromAssembly(asm);
        //            }
        //        }
                
        //    }
        //    catch (Exception ex)
        //    {
        //        Console.WriteLine(ex.Message);
        //    }

        //    //Load the RoM Framework Hacks
        //    try
        //    {
        //        Assembly asm = Assembly.GetExecutingAssembly();
        //        LoadHacksFromAssembly(asm);
        //    }
        //    catch (Exception ex)
        //    {
        //        Console.WriteLine(ex.Message);
        //    }
        //}

        //private void LoadHacksFromAssembly(Assembly asm)
        //{
        //    Type[] types = asm.GetTypes();
        //    foreach (HackBase tempHack in
        //        types.Where(type => type.IsClass && type.IsSubclassOf(typeof (HackBase))).Select(type => Activator.CreateInstance(type, m_Client) as HackBase))
        //    {
        //        if (tempHack == null)
        //        {
        //            throw new Exception("Failed to add hack");
        //        }
        //        foreach (var hotKey in tempHack.CurrentHotKeys)
        //        {
        //            if (Hacks.ContainsKey(hotKey))
        //            {
        //                throw new Exception("Error, the specified hotkey is already assigned");
        //            }
        //            Hacks.Add(hotKey, tempHack);
        //        }
        //    }
        //    //foreach (var type in types)
        //    //{
        //    //    // Here's some fairly simple stuff.
        //    //    if (type.IsClass && type.IsSubclassOf(typeof(HackBase)))
        //    //    {
        //    //        // Create the Hack using the Activator class.
        //    //        HackBase tempHack = Activator.CreateInstance(type, m_Client) as HackBase;
        //    //        // Make sure we're not using two of the same HotKey.
        //    //        // (That would be bad!)
        //    //        if (tempHack == null)
        //    //        {
        //    //            throw new Exception("Failed to add hack");
        //    //        }
        //    //        if (Hacks.Any(hackBase => Hacks.ContainsKey(hackBase.Key)))
        //    //        {
        //    //            throw new Exception(("Error, the specified hotkey is already assigned"));
        //    //        }
        //    //        foreach (var hotKey in tempHack.CurrentHotKeys)
        //    //        {
        //    //            Hacks.Add(hotKey, tempHack);
        //    //        }
        //    //    }

        //    //}
        //}

        //public void PassHotKey(Keys k)
        //{
        //    try
        //    {
        //        HackBase hack;
        //        Hacks.TryGetValue(k, out hack);
        //        hack.Toggle(k);
        //    }
        //    catch (Exception)
        //    {
        //    }
        //}

        #endregion

        #region SpeedHack

        /// <summary>
        /// Increases the Player speed
        /// </summary>
        /// <param name="speed">The value to increase the speed by (defaults to 1)</param>
        public void IncreaseSpeed(float speed = 1)
        {
            m_Client.Player.Speed += speed;
        }

        /// <summary>
        /// Decreases the Player Speed
        /// </summary>
        /// <param name="speed">The value to decrease the speed by (defaults to 1)</param>
        public void DecreaseSpeed(float speed = 1)
        {
            m_Client.Player.Speed -= speed;
        }

        /// <summary>
        /// Resets the Player speed
        /// </summary>
        public void ResetSpeed()
        {
            m_Client.Player.Speed = (OriginalSpeed != 0) ? OriginalSpeed : 50;
        }

        #endregion

        #region NoFall

        /// <summary>
        /// Enables the No Fall
        /// </summary>
        public void EnableNoFall()
        {
            if (NoFallEnabled)
                return;
            m_NoFallOriginalBytes = m_Client.RoM.ReadBytes(m_Client.Pointers.PatternSearcher.AddressesDictionary["NoFall"], (int)FunctionSizes.NoFall);
            m_Client.RoM.Asm.Clear();
            for (int i = 0; i < (int)FunctionSizes.NoFall; i++)
            {
                m_Client.RoM.Asm.AddLine("nop");
            }
            var m_NoFallBytes = m_Client.RoM.Asm.Assemble();
            m_Client.RoM.WriteBytes(m_Client.Pointers.PatternSearcher.AddressesDictionary["NoFall"], m_NoFallBytes);
            NoFallEnabled = true;
        }

        /// <summary>
        /// Disables No Fall
        /// </summary>
        public void DisableNoFall()
        {
            if (!NoFallEnabled)
                return;
            m_Client.RoM.WriteBytes(m_Client.Pointers.PatternSearcher.AddressesDictionary["NoFall"], m_NoFallOriginalBytes);
            NoFallEnabled = false;
        }

        /// <summary>
        /// Toggles No Fall
        /// </summary>
        public void ToggleNoFall()
        {
            if (NoFallEnabled)
            {
                DisableNoFall();
            }
            else if (!NoFallEnabled)
            {
                EnableNoFall();
            }
        }

        #endregion

        #region SwimHack

        /// <summary>
        /// Enables Swim Hack
        /// </summary>
        public void EnableSwimHack()
        {
            try
            {
                if (SwimHackEnabled)
                    return;
                m_SwimHackOriginalBytes = m_Client.RoM.ReadBytes(m_Client.Pointers.PatternSearcher.AddressesDictionary["SwimHackFunction"], (int)FunctionSizes.SwimHack);
                m_Client.RoM.Asm.Clear();
                for (int i = 0; i < (int)FunctionSizes.SwimHack; i++)
                {
                    m_Client.RoM.Asm.AddLine("nop");
                }
                var m_SwimHackBytes = m_Client.RoM.Asm.Assemble();
                m_Client.RoM.WriteBytes(m_Client.Pointers.PatternSearcher.AddressesDictionary["SwimHackFunction"], m_SwimHackBytes);
                m_Client.Player.SwimFlag = 3;
                SwimHackEnabled = true;
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        /// <summary>
        /// Disables Swim Hack
        /// </summary>
        public void DisableSwimHack()
        {
            if (!SwimHackEnabled)
                return;
            m_Client.RoM.WriteBytes(m_Client.Pointers.PatternSearcher.AddressesDictionary["SwimHackFunction"], m_SwimHackOriginalBytes);
            m_Client.Player.SwimFlag = 0;
            SwimHackEnabled = false;
        }

        /// <summary>
        /// Toggles Swim hack
        /// </summary>
        public void ToggleSwimHack()
        {
            if (SwimHackEnabled)
            {
                DisableSwimHack();
            }
            else if (!SwimHackEnabled)
            {
                EnableSwimHack();
            }
        }

        #endregion

        #region StealthHack

        /// <summary>
        /// Enables Stealth Hack
        /// </summary>
        public void EnableStealthHack()
        {
            if (StealthHackEnabled)
                return;
            var readBytes = m_Client.RoM.ReadByte(m_Client.Pointers.PatternSearcher.AddressesDictionary["StealthHack"]);
            if (readBytes != 1)
            {
                Console.Instance.AddLine("StealthHack error");
                return;
            }
            m_Client.RoM.WriteByte(m_Client.Pointers.PatternSearcher.AddressesDictionary["StealthHack"], 0x00);
            StealthHackEnabled = true;
        }

        /// <summary>
        /// Disables Stealth Hack
        /// </summary>
        public void DisableStealthHack()
        {
            if (!StealthHackEnabled)
                return;
            var readBytes = m_Client.RoM.ReadByte(m_Client.Pointers.PatternSearcher.AddressesDictionary["StealthHack"]);
            if (readBytes !=0x00)
            {
                Console.Instance.AddLine("StealthHack error");
                return;
            }
            m_Client.RoM.WriteByte(m_Client.Pointers.PatternSearcher.AddressesDictionary["StealthHack"], 0x01);
            StealthHackEnabled = false;
        }

        /// <summary>
        /// Toggles Stealth Hack
        /// </summary>
        public void ToggleStealthHack()
        {
            if (StealthHackEnabled)
            {
                DisableStealthHack();
            }
            else if (!StealthHackEnabled)
            {
                EnableStealthHack();
            }
        }

        #endregion

        #region WallClimb

        /// <summary>
        /// Enables Wall Climb
        /// </summary>
        /// <param name="angle">The angle to set (defaults to 0.1)</param>
        public void EnableWallClimb(float angle = 0.1f)
        {
            if (WallClimbEnabled) return;
            m_Client.Player.ClimbAngle = angle;
            WallClimbEnabled = true;
        }

        /// <summary>
        /// Disables Wall Climb
        /// </summary>
        public void DisableWallClimb()
        {
            if (!WallClimbEnabled) return;
            m_Client.Player.ClimbAngle = OriginalWallClimb;
            WallClimbEnabled = false;
        }

        /// <summary>
        /// Toggles Wall Climb
        /// </summary>
        public void ToggleWallClimb()
        {
            if (WallClimbEnabled)
            {
                DisableWallClimb();
            }
            else if (!WallClimbEnabled)
            {
                EnableWallClimb(WallClimbAngle);
            }
        }

        #endregion
    }

    public enum FunctionSizes
    {
        SwimHack = 0x06,
        NoFall = 0x0C
    }
}
